#!/usr/bin/python
# -*- coding:utf-8 -*-
"""
@file: recfacial.py
"""

import json
import time
import pylab
import matplotlib.pyplot           as plt
import matplotlib.cm               as cm
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib
import numpy
from   numpy        import *
from   PIL          import Image
from   random       import randint
from   matplotlib   import *
from   config       import Config
from   os           import environ, getpid
from   os.path      import join
from   clasificador import *

cltest = list()
cltrain = list()

def pca(vfaces, q = None):
    """
    Interfaz para el algoritmo PCA

    Por razones de compatibilidad con el código legado, se defina esta interfaz
    que recupera los valores del algoritmo PCA, y selecciona los valores que
    son usados en este programa.

    @param vfaces: Matriz que contiene la información de los rostros
    @param q: Número entero (parámetro opcional)

    @retval: Tupla con cuatro elementos:

        - Matriz de covarianza
        - Valores propios
        - Imágen media
        - Eigencaras
    """

    matriz = numpy.matrix(vfaces)
    matriz = matriz.transpose()
    msg  = "Aplicando el algoritmo PCA, tamaño de la matriz:"
    smat = "%d x %d" % matriz.shape
    muestra(msg, smat)
    P = acp(matriz, q)
    muestra("Algoritmo PCA terminado")

    return P.Yfg, P.d, P.mf, P.Vp

def arrayor(arrA, arrB):
    """
    Función auxiliar para el algoritmo de K vecinos más cercanos
    \param arrA: secuencia
    @param arrB: secuencia

    @return: una secuencia de unos y ceros con los resultados de la ooperación
    "or" sobre cada pareja de elementos (a,b) con a em arrA y b en arrB
    @rtype: secuencia
    """

    aux = list()

    for a,b in zip(arrA, arrB):
        item = 0 if a == b else 1
        aux.append(item)

    res = sum(aux) / float(len(aux)) * 100
    return res

def num_col( ndir,narch ):
    """
    Calcula en qué columna de la matriz de datos va a almacenarse una imagen

    @param ndir: Número de carpeta
    @type dir: int
    @param narch: Número de imágen
    @type narch: int

    @return: (ndir - 1) * <fotos por person> + narch - 1
    @rtype: int
    """

    conf   = Config()
    ndir  -= 1
    narch -= 1
    num = ndir * conf.personas + narch
    return num

def carga( ruta, rango ):
    """
    Genera las rutas hacia las imágenes que forman parte de una base de datos de
    rostros

    @param ruta: Ruta hacia la carpeta que contiene o contenerá las imágenes. de
    preferencia tiene que ser una carpeta vacía.
    @type ruta: Cadena de carateres.

    @param rango: Rango de imágenes que serán cargadas
    (M{rango = (a,b) | a < b}).
    Por ejemplo, si rango es igual a (1,50) se generaran imágenes cuyos nombres
    serán: img001.pgm, img002.pgm, ... img049.pgm, img050.pgm, etc.

    @type rango: tupla de dos números enteros

    @return: Las rutas hacia las imágenes que forman la base de datos
    @rtype: Lista de cadenas de caracteres
    """

    imlist = list()
    a, b = rango
    for k in range(a,b) :
        img_file = '%s/img%03d.pgm' % (ruta,k)
        imlist.append(img_file)
    return imlist

def creaMatriz(ruta, carpetas):
    """
    Carga las imágenes que forman parte de la base de datos y devuelve una
    partición de estas que contiene un conjunto de datos para entrenamiento y
    otro para hacer pruebas

    @param imgpath: Ruta en donde se encuentra la base de datos
    @type: Cadena de caracteres

    @return: Un tupla de tres elementos de la forma (L, P, E) donde:
      - list_total: El conjunto completo de imágenes
      - imlist_test: Subconjunto de list_total, que contiene el conjunto de
        pruebas.
      - imlist: Subconjunto de list_total, que contiene el conjunto de
        entrenamiento.
    """
    randir = 0
    conf = Config()
    ini, mit, fin = 1, conf.imgent + 1, conf.imagenes + 1

    list_total  = list()
    imlist      = list()
    imlist_test = list()

    imgpath = join( ruta, 'img%d' )

    for k in range( 1, conf.personas + 1):
        lsta = carga( imgpath % k, (ini, mit) )
        lstb = carga( imgpath % k, (mit, fin) )

        imlist_test.extend(lsta)
        imlist.extend(lstb)
        # Las clases de prueba son los índices de cada una de las columnas
        # seleccionadas como patrones de prueba, análogamente, las clases de
        # entrenamiento son los índices de las columnas seleccionadas como
        # patrones de entrenamiento.
        #
        # Las variables cltest y cltrain con listas globales definidas en las
        # 20 y 21 de el archivo recfacial.py (este archivo).
        #
        # Calculando los patrones de prueba
        cltest.extend ( [ k-1 for n in range(conf.imgent) ] )
        # Calculando los patrones de entrenamiento
        cltrain.extend( [ k-1 for n in range(conf.imgent,conf.imagenes) ] )

        list_total += lsta + lstb

    return list_total, imlist_test, imlist



def muestraimg(img, titulo):
    """
    Muestra una imagen en pantalla

    @param img: Objeto que representa una imagen.
    @type img: Instancia de la clase Image

    @param titulo: Título que aparecerá en la ventana que muestre la imagen
    @type titulo:
    """
    pylab.figure()
    pylab.title(titulo)
    pylab.gray()
    pylab.axis('off')

    pylab.imshow(img)



def errorCuadratico(S):
    """
    El error medio cuadratico esta dado por la suma de todos los
    eigenvalores menos la suma de los q mas grandes eigenvalores.

    @param S: valores propios
    @type S: secuencia
    """

    conf = Config()
    tam= len(S)
    S= list(S)
    dist    = [ k / (tam+5.0) for k in range(tam+6) ]
    emc     = (tam+1) * [0.0]
    energia = (tam+1) * [0.0]
    EOpt    = (tam+1) * [0.0]


    for q in range(tam+1):
        emc[q]=sum(S[q:tam-1])
        energia[q]=sum(S[0:q])/sum(S[0:tam-1])

        # energia de perdida de la varianza presente en el
        #vector de caracteristicas original
        EOpt[q]=sqrt(((dist[q])*(dist[q]))+
        ((1-energia[q])*(1-energia[q])))

    a     = min(EOpt)
    vpmin = EOpt.index(min(EOpt))
    nu    = energia[vpmin]

    return a, vpmin, nu, EOpt, energia

def main(args):
    """
    """

    conf = Config()
    conf.configura( join( args ,'refac.conf') )

    vfaces, pruebas, entrena = creaMatriz( args, range(1,conf.personas + 1) )

    #abrir una imagen para obtener su tamaño
    im = numpy.array(Image.open(vfaces[0]))
    #obtener el tamaño de las imágenes
    m, n = im.shape

    #obtener el número de imágenes
    imnent = len(entrena)

    #crear matriz para almacenar todas las imágenes aplanado
    enmatrix = numpy.array(
        [numpy.array(Image.open(entrena[i])).flatten() for i in range(imnent)],
        'f')

    #obtener el número de imágenes
    imnprb = len(pruebas)

    prbmatrix = numpy.array(
        [numpy.array(Image.open(pruebas[i])).flatten() for i in range(imnprb)],
        'f')

    # realizar pca
    V,S,immean, eigencaras = pca(enmatrix)

    #Imagen media
    immean = immean.reshape(m,n)

    #-------------------------------------------------------------------------
    #muestra figura 1
    #mostrar la imagen media
    muestra("Construyendo figura 1: Imágen media...")
    muestraimg( immean, "Imagen media" )

    #-------------------------------------------------------------------------
    #muestra figura 2

    muestra("Construyendo figura 2: Error cuadrático medio...")
    _, vpmin, nu, EOpt, energia = errorCuadratico(S)
    plt.figure()
    plt.title('Energia capturada VS Numero de Eigenvectores conservados')
    plt.plot(energia,'-*m', markerfacecolor='pink',markeredgecolor='pink')
    plt.plot( vpmin, nu, '-*m', linewidth=10, markerfacecolor='black',
        markeredgecolor='black', markersize=10)
    plt.axis([0,200,0,1])
    plt.xlabel('Numero de Eigenvectores')
    plt.ylabel('Energia capturada por los Eigenvectores')

    archivo = join( environ["DIRSALIDA"], 'energia.txt')
    linea = "{0:>7} {1:>15}\n"

    fp = open( archivo, 'a' )
    fp.write( time.ctime() + '\n' )
    fp.write( linea.format("Vector", "Energía")) # Encabezado

    for i, e in enumerate( energia ) :
        fp.write(linea.format(i+1,e))

    fp.close()

    archivo = join( environ["DIRSALIDA"], 'e_optima.txt' )
    fp = open( archivo, 'a' )
    fp.write( time.ctime() + '\n' )
    fp.write( linea.format("Vector", "Energía")) # Encabezado
    fp.write( linea.format( vpmin, nu ) )
    fp.close()

    #-------------------------------------------------------------------------

    # Calculo de PCA con la cantidad de eigenvectores con energia optima

    V,S,immean1, eigencaras = pca(enmatrix,vpmin-1)

    #-------------------------------------------------------------------------
    # muestra figura 3
    # Grafica las primeras eigencaras
    #
    #                        ====== Importante ======
    #
    # En la versión original (MATLAB) se grafican 15 eigencaras, pero en esta
    # implementación, solo se calcularon 9, esto puedde ser un indicador impor-
    # tante ¿Porqué nueve?
    #

    muestra( "Construyendo figura 3: Eigencaras..." )
    em, en = eigencaras.shape
    neig   = 15 if en > 15 else en

    muestra("Mostrando las primeras %d eigencaras" % neig)
    pylab.figure()
    pylab.plot()
    pylab.title("Eigencaras")
    pylab.axis('off')

    for i in range( neig ):
        muestra( "Desplegando eigencara:", "%2d/%2d" % (i + 1, neig) )
        eigcara = eigencaras[:,i]
        mode = eigencaras[:,i].reshape((m,n))
        pylab.subplot(3,5,i+1)
        pylab.title("Eigencara %d" % (i + 1))
        pylab.gray()
        pylab.axis('off')
        pylab.imshow(mode)

    #-------------------------------------------------------------------------
    #muestra figura 5
    #graficos 3D de las tres primeros componentes del espacio reducido para
    #cada imagen de entrenamiento

    muestra("Construyendo figura 4: Componentes principales...")


    fig = pylab.figure ()
    _, col = V.shape
    ax = p3.Axes3D (fig)
    x = numpy.array(V[0,:], numpy.int).reshape((col,))
    y = numpy.array(V[1,:], numpy.int).reshape((col,))
    z = numpy.array(V[2,:], numpy.int).reshape((col,))

    #color= pylab.cm.get_cmap("Greens")
    color = matplotlib.colors.Colormap("Greens", N=256)
    c = matplotlib.cm.get_cmap(color)

    ax.scatter(x, y, z, zdir='z',s=5, c='r')
    titulo  = 'Distribucion de los 3 primeros componentes de cada cara '
    titulo += 'determinados con PCA'
    ax.set_title(titulo)
    ax.set_xlabel('X - Primer componente principal')
    ax.set_ylabel('Y - Segundo componente principal')
    ax.set_zlabel('Z - Tercer componente principal')
    ax.view_init(35,35) # esta instrucción es para girar la perspectiva de la
                        # gráfica

    fig.add_axes(ax)

    cl_train = array(cltrain)

    # Calculando las tasas de error
    t_e = list()
    msg = "Llamando al algoritmo KNN con los patrones de entrenamiento"
    muestra(msg)
    muestra("El algoritmo será llamado %d veces" % conf.knn )
    for k in range(1, conf.knn + 1):
        clases = miKNN(V, cl_train, V, k )
        t_e.append( arrayor( clases, cl_train ) )

    # Cálculos previos al segundo cálculo de tasas de error
    #

    nt, mt = prbmatrix.shape
    mf = numpy.matrix(numpy.repeat( immean1, nt ))
    mf = mf.reshape( mt, nt )

    prbm = numpy.matrix( prbmatrix.transpose() )
    B = prbm - mf

    Vtest = eigencaras.transpose() * B

    # Calculando las tasas de error
    t_e_test = list()
    muestra("Llamando al algoritmo de kvecinos más cercanos")
    muestra("El algoritmo será llamado %d veces" % conf.knn )
    for k in range(1, conf.knn + 1):
        #ipdb.set_trace()
        clases_test = miKNN(V, cl_train, Vtest, k )
        t_e_test.append( arrayor( clases_test, cltest ) )

    # Graficando las tasas de error

    muestra("Construyendo figura 5: Tasas de error...")
    plt.figure()
    plt.title('Validacion cruzada del calsificador k vecinos mas cercanos - PCA')
    plt.xlabel('K vecinos mas cercanos')
    plt.ylabel('Tasa de error')
    plt.plot( t_e     , '-*k', markerfacecolor='red'  , markeredgecolor='red'  )
    plt.plot( t_e_test, '-*r', markerfacecolor='black', markeredgecolor='black')

    archivo = join( environ["DIRSALIDA"], 'tasas.txt')
    linea = "{0:>7} {1:>12} {2:>12}\n"

    fp = open( archivo, 'a' )
    fp.write( time.ctime() + '\n' )
    fp.write( linea.format("Vecinos", "Error 1", "Error 2")) # Encabezado

    fp = open( archivo, 'w' )
    for i, (a,b) in enumerate( zip( t_e, t_e_test ) ) :
        fp.write(linea.format(i+1, a, b))
    fp.close()

    muestra("Desplegando las figuras generadas...")
    pylab.show()
    muestra("¡Programa terminado correctamente!")

if __name__ == '__main__':

    retpid()

    try:
        bd = join( environ["DIRBD"], "bdorl" )
    except KeyError:
        bd = "./bdorl"

    muestra( "Usando la base de datos ubicada en: %s" % bd )
    main(bd)

